package com.example.untils;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import net.sourceforge.pinyin4j.PinyinHelper;
import net.sourceforge.pinyin4j.format.HanyuPinyinCaseType;
import net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat;
import net.sourceforge.pinyin4j.format.HanyuPinyinToneType;
import net.sourceforge.pinyin4j.format.HanyuPinyinVCharType;
import net.sourceforge.pinyin4j.format.exception.BadHanyuPinyinOutputFormatCombination;

import org.springframework.util.CollectionUtils;
/**
 * 电子质证
 * 
 * @author lli 
 *
 * @version 1.0
 *
 */
public class ChineseMatchUtil {
    /** 中文所在匹配权重 */
    private static final int CHNIESE_SCORE = 3;
    /** 数字所在匹配权重 */
    private static final int NUM_SCORE = 1;

    /**
     * 
     * @description 语音模糊匹配
     * @author lli
     * @create 2017年4月5日下午1:41:02
     * @version 1.0
     * @param str
     *            待匹配中文字符串
     * @param matchList
     *            中文字符串池
     * @param threshold
     *            相似度阈值 (0,1]
     * @return List
     */
    public static List<String> match(final String str,
            final List<String> matchList, final double threshold) {
        // 1.数字转中文 2.剔除非中文和非数字3.按字切分
        List<String> inSet = getSegmentAll(str);
        // 中文转拼音
        List<String> pinyins = getPinyin(inSet);
        if (pinyins == null || pinyins.size() == 0) {
            return Collections.emptyList();
        }
        // 返回结果集
        List<String> maxScoreMcList = new ArrayList<String>();
        // 最大得分率
        BigDecimal maxrate = BigDecimal.ZERO;
        // 最大命中字数
        int maxscore = 0;
        int maxzc = 100;
        for (String den : matchList) {
            // 获取数字所在字符串的位置
            Set<Integer> dennumidx = getNumericSet(den);
            // 字符串中数字个数
            int numcount = dennumidx.size();
            // 权重 汉字 *2 数字 1
            int mlength = (StringUtil.matchChineseAndNum(den).length() - numcount)
                    * CHNIESE_SCORE + numcount * NUM_SCORE;
            // 1.数字转中文 2.剔除非中文和非数字3.按字切分
            List<String> deSet = getSegmentAll(den);
            // 语义所对应索引位置
            Map<String, List<Integer>> tempmatchmap = new LinkedHashMap<String, List<Integer>>();
            // 文档名称所对应的索引位置
            Map<String, List<Integer>> tempdenmatchmap = new LinkedHashMap<String, List<Integer>>();

            // 语义所对应索引位置
            Map<String, List<Integer>> matchmap = new LinkedHashMap<String, List<Integer>>();
            // 文档名称所对应的索引位置
            Map<String, List<Integer>> denmatchmap = new LinkedHashMap<String, List<Integer>>();

            // 获取命中索引位置
            getMatchIndex(pinyins, deSet, tempmatchmap, tempdenmatchmap);

            removeInvalid(tempmatchmap, tempdenmatchmap, matchmap, denmatchmap);

            // 转写结果与文档名称最小偏移
            int py = 20;
            // 转写结果与文档名称最小偏移 对应文档索引结果栈 命中字符下标
            List<Integer> resultstack = new ArrayList<Integer>();
            List<List<Integer>> strss = mapToList(matchmap);
            List<List<Integer>> denss = mapToList(denmatchmap);
            if (strss.size() > 0) {
                // 转写下标栈集合
                List<List<Integer>> stack = caluminpy(strss);
                // 文档下标栈集合
                List<List<Integer>> denstack = caluminpy(denss);
                for (List<Integer> sta : stack) {
                    for (List<Integer> densta : denstack) {

                        int jg = 0;
                        for (int ji = 0; ji < sta.size() - 1; ji++) {
                            int spy = sta.get(ji + 1) - sta.get(ji);
                            int denpy = densta.get(ji + 1) - densta.get(ji);
                            jg = jg + Math.abs(denpy - spy);
                        }
                        if (jg < py) {
                            py = jg;
                            resultstack = densta;
                        }

                    }
                }
            }

            List<Integer> resultList = new ArrayList<Integer>();
            for (Integer index : resultstack) {
                if (!resultList.contains(index)) {
                    resultList.add(index);
                }

            }
            // 求命中数字数
            dennumidx.retainAll(resultList);
            // 命中分数
            int num = (resultList.size() - dennumidx.size()) * CHNIESE_SCORE
                    + dennumidx.size() * NUM_SCORE;
            // 长句子命中字间距，应该是连续的
            int denpy = mpSum(resultList);
            // 计算得分
            if (num > 0) {
                double rate = num / (mlength * 1.00);
                BigDecimal rate_d = new BigDecimal(rate);
                if ((rate_d.compareTo(new BigDecimal(threshold)) == 1)
                        || (rate_d.compareTo(new BigDecimal(threshold)) != 1
                                && (num > (3 * CHNIESE_SCORE)) && py < 3 && denpy < resultList
                                .size() + 2)) {
                    if (py < maxzc) {
                        maxzc = py;
                        maxscore = num;
                        maxrate = rate_d;
                        maxScoreMcList.clear();
                        maxScoreMcList.add(den);
                    } else if (py == maxzc) {
                        if (rate_d.compareTo(maxrate) == 1) {
                            maxrate = rate_d;
                            maxscore = num;
                            maxScoreMcList.clear();
                            maxScoreMcList.add(den);
                        } else if (rate_d.compareTo(maxrate) == 0) {

                            if (num > maxscore) {
                                maxscore = num;
                                maxScoreMcList.clear();
                                maxScoreMcList.add(den);
                            } else if (maxscore == num) {
                                maxScoreMcList.add(den);
                            }

                        }

                    }

                }
            }

        }
        return maxScoreMcList;

    }

    /**
     * @description 移除偏移较大的命中字
     * @author lli
     * @create 2017年4月24日上午11:32:16
     * @version 1.0
     * @param matchmap
     * @param denmatchmap
     * @param matchmap1
     * @param denmatchmap1
     */
    @SuppressWarnings("unchecked")
    private static void removeInvalid(Map<String, List<Integer>> matchmap,
            Map<String, List<Integer>> denmatchmap,
            Map<String, List<Integer>> matchmap1,
            Map<String, List<Integer>> denmatchmap1) {
        Entry<String, List<Integer>>[] entryArray = denmatchmap.entrySet()
                .toArray(new Map.Entry[denmatchmap.size()]);
        for (int ji = 0; ji < denmatchmap.size() - 1; ji++) {
            Entry<String, List<Integer>> entry = entryArray[ji];
            Entry<String, List<Integer>> lastentry = entryArray[ji + 1];
            List<Integer> xbs = entry.getValue();
            List<Integer> lastxbs = lastentry.getValue();
            for (Integer xb : xbs) {
                for (Integer lastxb : lastxbs) {
                    int zj = lastxb - xb;
                    if (zj > 0) {
                        List<Integer> denxbs = matchmap.get(entry.getKey());
                        List<Integer> denlastxbs = matchmap.get(lastentry
                                .getKey());
                        for (Integer denxb : denxbs) {
                            for (Integer denlastxb : denlastxbs) {
                                if (zj == (denlastxb - denxb)) {
                                    if (denmatchmap1.get(entry.getKey()) == null) {
                                        List<Integer> txb = new ArrayList<Integer>();
                                        txb.add(xb);
                                        denmatchmap1.put(entry.getKey(), txb);
                                    } else {
                                        List<Integer> txb = denmatchmap1
                                                .get(entry.getKey());
                                        if (!txb.contains(xb)) {
                                            txb.add(xb);
                                        }
                                        denmatchmap1.put(entry.getKey(), txb);
                                    }

                                    if (denmatchmap1.get(lastentry.getKey()) == null) {
                                        List<Integer> txb = new ArrayList<Integer>();
                                        txb.add(lastxb);
                                        denmatchmap1.put(lastentry.getKey(),
                                                txb);
                                    } else {
                                        List<Integer> txb = denmatchmap1
                                                .get(lastentry.getKey());
                                        if (!txb.contains(lastxb)) {
                                            txb.add(lastxb);
                                        }
                                        denmatchmap1.put(lastentry.getKey(),
                                                txb);
                                    }

                                    if (matchmap1.get(entry.getKey()) == null) {
                                        List<Integer> txb = new ArrayList<Integer>();
                                        txb.add(denxb);
                                        matchmap1.put(entry.getKey(), txb);
                                    } else {
                                        List<Integer> txb = matchmap1.get(entry
                                                .getKey());
                                        if (!txb.contains(denxb)) {
                                            txb.add(denxb);
                                        }
                                        matchmap1.put(entry.getKey(), txb);
                                    }

                                    if (matchmap1.get(lastentry.getKey()) == null) {
                                        List<Integer> txb = new ArrayList<Integer>();
                                        txb.add(denlastxb);
                                        matchmap1.put(lastentry.getKey(), txb);
                                    } else {
                                        List<Integer> txb = matchmap1
                                                .get(lastentry.getKey());
                                        if (!txb.contains(denlastxb)) {
                                            txb.add(denlastxb);
                                        }
                                        matchmap1.put(lastentry.getKey(), txb);
                                    }
                                }
                            }

                        }
                    }
                }
            }
        }
    }

    /**
     * @description
     * @author lli
     * @create 2017年4月21日下午1:51:05
     * @version 1.0
     * @param matchmap
     * @return
     */
    private static List<List<Integer>> mapToList(
            Map<String, List<Integer>> matchmap) {
        List<List<Integer>> ss = new ArrayList<List<Integer>>();
        for (Entry<String, List<Integer>> entry : matchmap.entrySet()) {
            ss.add(entry.getValue());
        }
        return ss;
    }

    /**
     * @description 单轮冒泡排序求偏移和
     * @author lli
     * @create 2017年4月21日上午9:48:39
     * @version 1.0
     * @param sta
     */
    private static int mpSum(List<Integer> sta) {
        int sz = 0;
        for (int ii = 0; ii < 1; ii++) {
            for (int ji = 0; ji < sta.size() - 1 - ii; ji++) {
                int s = Math.abs(sta.get(ji + 1) - sta.get(ji));
                sz = sz + s;

            }
        }
        return sz;
    }

    /**
     * 
     * @description 获取数字所在字符串的位置
     * @author lli
     * @create 2017年4月20日下午5:20:02
     * @version 1.0
     * @param st
     * @return
     */
    public static Set<Integer> getNumericSet(String st) {
        String str = StringUtil.matchChineseAndNum(st);
        Set<Integer> result = new LinkedHashSet<Integer>();
        for (int i = str.length(); --i >= 0;) {
            char den = str.charAt(i);
            if (Character.isDigit(den)) {
                result.add(i);
            }
        }
        return result;
    }

    /**
     * @description
     * @author lli
     * @create 2017年4月19日下午3:13:54
     * @version 1.0
     * @param pinyins
     * @param deSet
     * @param matchmap
     */
    private static void getMatchIndex(List<String> pinyins, List<String> deSet,
            Map<String, List<Integer>> matchmap,
            Map<String, List<Integer>> denmatchmap) {
        Map<String, List<Integer>> tmpmatchmap = new LinkedHashMap<String, List<Integer>>();
        Map<String, List<Integer>> tmpdenmatchmap = new LinkedHashMap<String, List<Integer>>();
        int denidx = 0;
        for (String in : deSet) {
            if (!"".equals(in)) {
                String denstr = getPinYin(in);
                int idx = 0;
                for (String pinyin : pinyins) {
                    if (denstr.equals(pinyin)) {
                        if (tmpmatchmap.get(denstr) == null) {
                            List<Integer> xb = new ArrayList<Integer>();
                            xb.add(idx);
                            tmpmatchmap.put(denstr, xb);
                            matchmap.put(denstr + denidx, xb);
                        } else {
                            List<Integer> xb = tmpmatchmap.get(denstr);
                            if (!xb.contains(idx)) {
                                xb.add(idx);
                            }
                            tmpmatchmap.put(denstr, xb);
                            matchmap.put(denstr + denidx, xb);
                        }

                        if (tmpdenmatchmap.get(pinyin) == null) {
                            List<Integer> xb = new ArrayList<Integer>();
                            xb.add(denidx);
                            tmpdenmatchmap.put(pinyin, xb);
                            denmatchmap.put(pinyin + denidx, xb);
                        } else {
                            List<Integer> xb = tmpdenmatchmap.get(pinyin);
                            if (!xb.contains(denidx)) {
                                xb.add(denidx);
                            }
                            tmpdenmatchmap.put(pinyin, xb);
                            denmatchmap.put(pinyin + denidx, xb);
                        }

                    }
                    idx++;
                }
                denidx++;
            }

        }
    }

    /**
     * 
     * @description 获取字符串中数字个数
     * @author lli
     * @create 2017年4月20日下午5:19:20
     * @version 1.0
     * @param str
     * @return
     */
    public static int findNumCount(String str) {

        Matcher match = Pattern.compile("\\d").matcher(str);
        int count = 0;
        while (match.find()) {
            count++;
        }
        return count;

    }

    /**
     * @description 1.数字转中文 2.剔除非中文和非数字3.按字切分
     * @author lli
     * @create 2017年4月12日下午6:36:46
     * @version 1.0
     * @param str
     * @return
     */
    private static List<String> getSegmentAll(final String str) {
        String numstr = StringUtil.numToChinese(str);
        return chinesetoCharArray(StringUtil.matchChineseAndNum(numstr));
    }

    private static List<String> chinesetoCharArray(final String s) {
        List<String> result = new ArrayList<String>(Arrays.asList(s.split("")));
        return result;
    }

    /**
     * @description 中文转拼音
     * @author lli
     * @create 2017年4月5日下午12:52:05
     * @version 1.0
     * @param str
     *            String
     * @return Set
     */
    private static List<String> getPinyin(final Collection<String> inSet) {
        List<String> pinyins = new ArrayList<String>();
        for (String in : inSet) {
            if (!"".equals(in)) {
                pinyins.add(getPinYin(in));
            }

        }
        return pinyins;
    }

    // 将汉字转换为全拼
    public static String getPinYin(String src) {

        char[] t1 = null;
        t1 = src.toCharArray();
        String[] t2 = new String[t1.length];
        HanyuPinyinOutputFormat t3 = new HanyuPinyinOutputFormat();

        t3.setCaseType(HanyuPinyinCaseType.LOWERCASE);
        t3.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
        t3.setVCharType(HanyuPinyinVCharType.WITH_V);
        String t4 = "";
        int t0 = t1.length;
        try {
            for (int i = 0; i < t0; i++) {
                // 判断是否为汉字字符
                if (java.lang.Character.toString(t1[i]).matches(
                        "[\\u4E00-\\u9FA5]+")) {
                    t2 = PinyinHelper.toHanyuPinyinStringArray(t1[i], t3);
                    t4 += t2[0];
                } else
                    t4 += java.lang.Character.toString(t1[i]);
            }
            // System.out.println(t4);
            return t4;
        } catch (BadHanyuPinyinOutputFormatCombination e1) {
            e1.printStackTrace();
        }
        return t4;
    }

    /**
     * @description 根据下标集合求所有的分支栈
     * @author lli
     * @create 2017年4月21日上午2:08:01
     * @version 1.0
     * @param ss
     * @throws NumberFormatException
     */
    private static List<List<Integer>> caluminpy(List<List<Integer>> ss)
        throws NumberFormatException {
        List<List<Integer>> result = new ArrayList<List<Integer>>();
        // 分割线
        int min = 100;
        Stack<String> stack = new Stack<String>(); // 创建堆栈对象
        int i = 0;
        int j = 0;
        stack.push("0|0");
        while (true) {
            if (i < ss.size() - 1) {
                i++;
                stack.push(i + "|" + 0);
                List<Integer> stacklist = calupy(ss, min, stack, j);
                if (!CollectionUtils.isEmpty(stacklist)) {
                    result.add(stacklist);
                }

            } else if (j < ss.get(i).size() - 1) {
                String st = (String) stack.pop();
                i = Integer.valueOf(st.split("\\|")[0]);
                j = Integer.valueOf(st.split("\\|")[1]);
                j++;

                stack.push(i + "|" + j);
                List<Integer> stacklist = calupy(ss, min, stack, j);
                if (!CollectionUtils.isEmpty(stacklist)) {
                    result.add(stacklist);
                }

            } else {
                while (!stack.empty()) {
                    String st = (String) stack.pop();
                    i = Integer.valueOf(st.split("\\|")[0]);
                    j = Integer.valueOf(st.split("\\|")[1]);

                    if (j < ss.get(i).size() - 1) {
                        j++;
                        stack.push(i + "|" + j);
                        List<Integer> stacklist = calupy(ss, min, stack, j);
                        if (!CollectionUtils.isEmpty(stacklist)) {
                            result.add(stacklist);
                        }

                        break;
                    }
                }
            }

            if (stack.empty()) {
                break;
            }
        }
        return result;
    }

    /**
     * @description
     * @author lli
     * @create 2017年4月21日上午1:56:37
     * @version 1.0
     * @param ss
     * @param min
     * @param stack
     * @param j
     * @return
     * @throws NumberFormatException
     */
    private static List<Integer> calupy(List<List<Integer>> ss, int min,
            Stack<String> stack, int j) throws NumberFormatException {
        // stack. 计算偏移
        List<Integer> s32 = new ArrayList<Integer>();
        if (stack.size() == ss.size()) {
            Enumeration<String> items = stack.elements(); // 得到 stack
                                                          // 中的枚举对象

            while (items.hasMoreElements()) {
                String str = items.nextElement();
                Integer te = ss.get(Integer.valueOf(str.split("\\|")[0])).get(
                        Integer.valueOf(str.split("\\|")[1]));
                s32.add(te);
            }
        }
        return s32;
    }

}
